import UIKit

//数组中解析模型
//let json = """
//[
//    {
//        "name": "Banana",
//        "points": 200,
//        "description": "A banana grown in Ecuador."
//    },
//    {
//        "name": "Orange",
//        "points": 100
//    }
//]
//""".data(using: .utf8)!
//
//struct GroceryProduct: Codable{
//    var name: String
//    var points: Int
//    var description: String?
//}
//
//let decoder = JSONDecoder()
//let products = try decoder.decode([GroceryProduct].self, from: json)
//
//print("The following products are available:")
//
//for product in products {
//    print("\t\(product.name) (\(product.points)) points")
//    if let description = product.description {
//        print("\t\t\(description)")
//    }
//}

//改变字段名的解析
//let json = """
//[
//    {
//        "product_name": "Bananas",
//        "product_cost": 200,
//        "description": "A banana grown in Ecuador."
//    },
//    {
//        "product_name": "Oranges",
//        "product_cost": 100,
//        "description": "A juicy orange."
//    }
//]
//""".data(using: .utf8)!
//
//struct GroceryProduct: Codable{
//    var name: String
//    var points: Int
//    var description: String?
//
//    private enum CodingKeys: String, CodingKey{
//        case name = "product_name"
//        case points = "product_cost"
//        case description
//    }
//
//}
//
//let decoder = JSONDecoder()
//let products = try decoder.decode([GroceryProduct].self, from: json)
//
//print("The following products are available:")
//for product in products {
//    print("\t\(product.name) (\(product.points) points)")
//    if let description = product.description {
//        print("\t\t\(description)")
//    }
//}


//模型扩展  从另个一模型中获取数据
//struct GroceryStore {
//    var name: String
//    var products: [Product]
//
//    struct Product: Codable {
//        var name: String
//        var points: Int
//        var description: String?
//    }
//}
//
//let json = """
//[
//    {
//        "name": "Home Town Market",
//        "aisles": [
//            {
//                "name": "Produce",
//                "shelves": [
//                    {
//                        "name": "Discount Produce",
//                        "product": {
//                            "name": "Banana",
//                            "points": 200,
//                            "description": "A banana that's perfectly ripe."
//                        }
//                    }
//                ]
//            }
//        ]
//    },
//    {
//        "name": "Big City Market",
//        "aisles": [
//            {
//                "name": "Sale Aisle",
//                "shelves": [
//                    {
//                        "name": "Seasonal Sale",
//                        "product": {
//                            "name": "Chestnuts",
//                            "points": 700,
//                            "description": "Chestnuts that were roasted over an open fire."
//                        }
//                    },
//                    {
//                        "name": "Last Season's Clearance",
//                        "product": {
//                            "name": "Pumpkin Seeds",
//                            "points": 400,
//                            "description": "Seeds harvested from a pumpkin."
//                        }
//                    }
//                ]
//            }
//        ]
//    }
//]
//""".data(using: .utf8)!
//
//struct GroceryStoreService: Decodable{
//    let name: String
//    let aisles: [Aisle]
//
//    struct Aisle: Decodable{
//        let name: String
//        let shelves: [Shelve]
//
//        struct Shelve: Decodable {
//            let name: String
//            let product: GroceryStore.Product
//        }
//    }
//}
//
////扩展模型构造函数
//extension GroceryStore {
//
//    init(from service: GroceryStoreService) {
//        name = service.name
//        products = []
//
//        for aisle in service.aisles {
//            for shelf in aisle.shelves {
//                products.append(shelf.product)
//            }
//        }
//
//    }
//
//}
//
//let decode = JSONDecoder()
//let serviceStores = try decode.decode([GroceryStoreService].self, from: json)
//
//let stores = serviceStores.map{ GroceryStore(from: $0) }
//
//for store in stores {
//    print("\(store.name) is selling:")
//    for product in store.products {
//        print("\t\(product.name) (\(product.points) points)")
//        if let description = product.description {
//            print("\t\t\(description)")
//        }
//    }
//}

let json = """
{
    "Banana": {
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    "Orange": {
        "points": 100
    }
}
""".data(using: .utf8)!


struct GroceryStore {
    struct Product {
        let name: String
        let points: Int
        let description: String?
    }
    
    var products:[Product]
    
    init(products: [Product] = []) {
        self.products = products
    }
    
}

extension GroceryStore: Encodable {
    
    struct ProductKey: CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int?{ return nil }
        init?(intValue: Int) {
            return nil
        }
        
        static let points = ProductKey(stringValue: "points")
        static let description = ProductKey(stringValue: "description")
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: ProductKey.self)
        
        for product in products {
            let nameKey = ProductKey(stringValue: product.name)!
            var  productContainer = container.nestedContainer(keyedBy: ProductKey.self, forKey: nameKey)
    
            try productContainer.encode(product.points, forKey: GroceryStore.ProductKey.points!)
            try productContainer.encode(product.description, forKey: GroceryStore.ProductKey.description!)
            
        }
        
    }
    
}


var encode = JSONEncoder()
encode.outputFormatting = .prettyPrinted

let store = GroceryStore(products: [
    .init(name: "Grapes", points: 230, description: "A mixture of red and green grapes."),
    .init(name: "Lemons", points: 2300, description: "An extra sour lemon.")
    ])

print("The result of encoding a GroceryStore:")
let encodedStore = try encode.encode(store)
print(String(data: encodedStore, encoding: .utf8)!)
print()


extension GroceryStore: Decodable{
    public init(from decoder: Decoder) throws {
        var products = [Product]()
        let container = try decoder.container(keyedBy: ProductKey.self)
        for key in container.allKeys {
            let productContainer = try container.nestedContainer(keyedBy: ProductKey.self, forKey: key)
            let points = try productContainer.decode(Int.self, forKey: GroceryStore.ProductKey.points!)
            let description = try productContainer.decodeIfPresent(String.self, forKey: GroceryStore.ProductKey.description!)
            
            let product = Product(name: key.stringValue, points: points, description: description)
            products.append(product)
        }
        
        self.init(products: products)
        
    }
}


let decoder = JSONDecoder()
let decodedStore = try decoder.decode(GroceryStore.self, from: json)

print("The store is selling the following products:")
for product in decodedStore.products {
    print("\t\(product.name) (\(product.points) points)")
    if let description = product.description {
        print("\t\t\(description)")
    }
}

